package com.mikufufu.blog.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson2.JSON;
import com.mikufufu.blog.common.cache.RedisCache;
import com.mikufufu.blog.common.constant.ContentType;
import com.mikufufu.blog.common.constant.RedisKey;
import com.mikufufu.blog.common.entity.AjaxResult;
import com.mikufufu.blog.common.enums.SexType;
import com.mikufufu.blog.model.dto.ForgetPasswordParam;
import com.mikufufu.blog.model.dto.LoginEmailParam;
import com.mikufufu.blog.model.dto.LoginParam;
import com.mikufufu.blog.model.dto.api.RegisterParam;
import com.mikufufu.blog.model.entity.Role;
import com.mikufufu.blog.model.excel.UserExcel;
import com.mikufufu.blog.model.vo.api.UserVO;
import com.mikufufu.blog.security.TokenStore;
import com.mikufufu.blog.security.enums.Channel;
import com.mikufufu.blog.security.enums.RoleCode;
import com.mikufufu.blog.security.enums.UserStatus;
import com.mikufufu.blog.security.exception.AuthException;
import com.mikufufu.blog.security.utils.AuthUtils;
import com.mikufufu.blog.service.RoleService;
import com.mikufufu.blog.utils.BeanUtil;
import com.mikufufu.blog.utils.EncryptionUtils;
import com.mikufufu.blog.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mikufufu.blog.mapper.UserMapper;
import com.mikufufu.blog.model.entity.User;
import com.mikufufu.blog.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户表 服务实现类
 * 
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private RoleService roleService;

    @Override
    public String login(LoginParam loginParam, Channel channel) {
        String account = loginParam.getAccount();
        boolean isEmail = StringUtils.isEmail(account);
        String password = EncryptionUtils.sha256(loginParam.getPassword());
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(isEmail,User::getEmail,account)
                .eq(!isEmail,User::getUsername,account)
                .eq(User::getPassword,password)
        );
        if (StringUtils.isEmpty(user)) {
            throw new AuthException("user.username.password.invalid");
        }
        checkUser(user,channel);
        return TokenStore.generateToken(user);
    }

    private void checkUser(User user, Channel channel) {
        if (UserStatus.DISABLED.getCode().equals(user.getStatus())) {
            throw new AuthException("user.disabled");
        }
        // 根据用户角色获取角色代码
        String roleCode = roleService.getCodeById(user.getRole());
        if(StringUtils.isBlank(roleCode)){
            throw new AuthException("user.role.invalid");
        }
        switch (channel){
            case ADMIN:
                if(!RoleCode.getRoleCodeByCode(roleCode).equals(RoleCode.ROLE_ADMIN) && !RoleCode.getRoleCodeByCode(roleCode).equals(RoleCode.ROLE_SYSTEM_ADMIN)){
                    throw new AuthException("user.not.admin");
                }
                break;
            case WEB:
                if(!RoleCode.getRoleCodeByCode(roleCode).equals(RoleCode.ROLE_USER)){
                    throw new AuthException("user.do.not.login.admin");
                }
                break;
            default:
                throw new AuthException("channel.not.exist");
        }
    }

    @Override
    public String register(RegisterParam registerParam) {
        if (StringUtils.isNotEmpty(this.getOne(new LambdaQueryWrapper<User>().eq(User::getUsername, registerParam.getAccount())))) {
            throw new AuthException("user.username.exist");
        }
        String code = RedisCache.get(RedisKey.EMAIL_CODE + registerParam.getEmail(), String.class);

        if(StringUtils.isBlank(code) || !code.equals(registerParam.getCode())){
            throw new AuthException("user.captcha.error");
        }
        if(StringUtils.isEmpty(registerParam.getInviteCode())){
            throw new AuthException("invite.code.is.null");
        }
        Map userMap = RedisCache.get(RedisKey.INVITATION_CODE + registerParam.getInviteCode(), Map.class);
        if(StringUtils.isEmpty(userMap)){
            throw new AuthException("invite.code.error");
        }
        if(roleService.getCodeById((Integer) userMap.get("roleId")).contains("admin")){
            throw new AuthException("invite.code.invalid");
        }
        User user = new User();
        user.setUsername(registerParam.getAccount());
        String password = EncryptionUtils.sha256(registerParam.getPassword());
        user.setPassword(password);
        user.setEmail(registerParam.getEmail());
        user.setAvatar(registerParam.getAvatar());
        //默认为普通用户
        Role role = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getRoleCode, RoleCode.ROLE_USER.getCode()));
        if(StringUtils.isEmpty(role)){
            user.setRole(role.getId());
        }
        this.save(user);
        return TokenStore.generateToken(user);
    }

    @Override
    public Boolean logout() {
        TokenStore.deleteToken(AuthUtils.getToken());
        return true;
    }

    @Override
    public UserVO getUserVOById(Integer id) {
        User user = this.getById(id);
        user.setUsername(null);
        user.setEmail(null);
        user.setPhoneNumber(null);
        return UserVO.convert(user);
    }

    @Override
    public String loginEmail(LoginEmailParam loginEmailParam, Channel channel) {
        String code = loginEmailParam.getCode();
        String emailCode = RedisCache.get(RedisKey.EMAIL_CODE + loginEmailParam.getEmail(), String.class);
        if(StringUtils.isBlank(emailCode) || !emailCode.equals(code)){
            throw new AuthException("user.captcha.error");
        }
        User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, loginEmailParam.getEmail()));
        if (StringUtils.isEmpty(user)) {
            throw new AuthException("user.not.exist");
        }
        checkUser(user,channel);
        return TokenStore.generateToken(user);
    }

    @Override
    public boolean forgetPassword(ForgetPasswordParam forgetPasswordParam) {

        String email = forgetPasswordParam.getEmail();

        String code = RedisCache.get(RedisKey.EMAIL_CODE + email, String.class);

        if(StringUtils.isBlank(code) || !code.equals(forgetPasswordParam.getCode())){
            throw new AuthException("user.captcha.error");
        }

        if(!forgetPasswordParam.getNewPassword().equals(forgetPasswordParam.getConfirmPassword())){
            throw new AuthException("user.password.match");
        }

        User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, email).eq(User::getStatus,UserStatus.NORMAL ));
        if(StringUtils.isEmpty(user)){
            throw new AuthException("user.not.exist");
        }

        if(user.getPassword().equals(EncryptionUtils.sha256(forgetPasswordParam.getNewPassword()))){
            throw new AuthException("user.password.same");
        }

        user.setPassword(EncryptionUtils.sha256(forgetPasswordParam.getNewPassword()));
        return this.updateById(user);

    }

    @Override
    public void export(HttpServletResponse response) {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("用户列表", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");


            List<UserExcel> list = this.list().stream().map(user -> {
                UserExcel userExcel = BeanUtil.copy(user, UserExcel.class);
                if (Objects.nonNull(user.getGender())) {
                    userExcel.setGender(SexType.getName(user.getGender()));
                }
                if (Objects.nonNull(user.getStatus())) {
                    userExcel.setStatus(UserStatus.getName(user.getStatus()));
                }
                if (Objects.nonNull(user.getRole())) {
                    RoleCode roleCodeEnum = RoleCode.getRoleCodeByValue(user.getRole());
                    userExcel.setRole(roleCodeEnum != null ? roleCodeEnum.getDesc() : "");
                }
                return userExcel;
            }).collect(Collectors.toList());

            // 这里需要设置不关闭流
            EasyExcel.write(response.getOutputStream(), UserExcel.class)
                    .autoCloseStream(Boolean.FALSE)
                    .sheet("用户信息")
                    .doWrite(list);

        } catch (Exception e) {
            log.error("下载文件失败 {}", e.getMessage(),e);
            // 重置response
            response.reset();
            response.setContentType(ContentType.JSON);
            response.setCharacterEncoding("utf-8");
            try {
                response.getWriter().println(JSON.toJSONString(AjaxResult.error("下载文件失败："+e.getMessage())));
            }catch (IOException io){
                throw new RuntimeException(io);
            }
        }
    }

    @Override
    public String generateInvitationCode() {
        User user = this.getById(AuthUtils.getUserId());
        Map<String, Object> userMap = new HashMap<>();
        userMap.put("userId", user.getId());
        userMap.put("roleId", user.getRole());
        userMap.put("nikeName", user.getNickname());
        String invitationCode = UUID.randomUUID().toString();
        RedisCache.set(RedisKey.INVITATION_CODE + invitationCode, userMap);
        return invitationCode;
    }

}
